﻿Imports MapWindow.Interfaces
Imports MapWinGIS
Imports System.IO

Public Class ski
    Implements IPlugin

    Private _mapWin As MapWindow.Interfaces.IMapWin

    Private _boolDraw As Boolean = False
    Private _boolFirstPointClick As Boolean = False

    Private _curX As Double = -1
    Private _curY As Double = -1

    Private _lastStartX As Double = -1
    Private _lastStartY As Double = -1

    Private _listOfPoints As New List(Of MapWinGIS.Point)

    Private _drawingHandle As Integer = -1

    Private _table As New DataTable

    'the last created ski path shapefile
    Private _lastSkiShapefile As Shapefile

    'the DEM grid
    Public Shared DemGrid As New Grid

    'menu item values
    Private _menuDrawPath As Boolean = False
    Private _menuDrawGraph As Boolean = False

    Public ReadOnly Property Author() As String Implements MapWindow.Interfaces.IPlugin.Author
        Get
            Return "Jiri, Teva"
        End Get
    End Property

    Public ReadOnly Property BuildDate() As String Implements MapWindow.Interfaces.IPlugin.BuildDate
        Get
            Return DateTime.Now
        End Get
    End Property

    Public ReadOnly Property Description() As String Implements MapWindow.Interfaces.IPlugin.Description
        Get
            Return "Find the best ski path"
        End Get
    End Property

    Public Sub Initialize(ByVal MapWin As MapWindow.Interfaces.IMapWin, ByVal ParentHandle As Integer) Implements MapWindow.Interfaces.IPlugin.Initialize
        _mapWin = MapWin
        _mapWin.Menus.AddMenu("Ski Path Finder")
        _mapWin.Menus.AddMenu("Load DEM", "Ski Path Finder")
        _mapWin.Menus.AddMenu("Draw Ski Path", "Ski Path Finder")
        _mapWin.Menus.AddMenu("Draw Graph", "Ski Path Finder")
        _mapWin.Menus.AddMenu("Compare Ski Paths", "Ski Path Finder")
    End Sub

    Public Sub ItemClicked(ByVal ItemName As String, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.ItemClicked
        If ItemName = "Load DEM" Then
            LoadDEM()

        ElseIf ItemName = "Draw Ski Path" Then

            'if the DEM is not loaded, show error message
            If _menuDrawPath = False Then
                MsgBox("Please load a DEM.", MsgBoxStyle.Information, "Ski Path Finder")
                Exit Sub
            End If

            'clear the list of points in the previous path
            _listOfPoints.Clear()

            _boolDraw = True
            _mapWin.View.CursorMode = MapWinGIS.tkCursorMode.cmNone
            _mapWin.View.MapCursor = MapWinGIS.tkCursor.crsrCross

        ElseIf ItemName = "Draw Graph" Then

            'if the DEM is not loaded, show error message
            If _menuDrawPath = False Then
                MsgBox("Please load a DEM.", MsgBoxStyle.Information, "Ski Path Finder")
                Exit Sub
            End If
            'if the ski path is not drawn, show error message
            If _menuDrawGraph = False Then
                MsgBox("Please draw a ski path.", MsgBoxStyle.Information, "Ski Path Finder")
                Exit Sub
            End If

            Dim frmGraph As New graph
            frmGraph.DrawGraph(_mapWin)
            frmGraph.Show()

        ElseIf ItemName = "Compare Ski Paths" Then

            'if the DEM is not loaded, show error message
            If _menuDrawPath = False Then
                MsgBox("Please load a DEM.", MsgBoxStyle.Information, "Ski Path Finder")
                Exit Sub
            End If
            'if the ski path is not drawn, show error message
            If _menuDrawGraph = False Then
                MsgBox("Please draw a ski path.", MsgBoxStyle.Information, "Ski Path Finder")
                Exit Sub
            End If

            Dim frmAnalyze As New Analyze()
            frmAnalyze.CompareSkiPaths(_mapWin)
            frmAnalyze.Show()

        End If
    End Sub


    

    Public Sub LayerRemoved(ByVal Handle As Integer) Implements MapWindow.Interfaces.IPlugin.LayerRemoved

    End Sub

    Public Sub LayersAdded(ByVal Layers() As MapWindow.Interfaces.Layer) Implements MapWindow.Interfaces.IPlugin.LayersAdded

    End Sub

    Public Sub LayersCleared() Implements MapWindow.Interfaces.IPlugin.LayersCleared

    End Sub

    Public Sub LayerSelected(ByVal Handle As Integer) Implements MapWindow.Interfaces.IPlugin.LayerSelected

    End Sub

    Public Sub LegendDoubleClick(ByVal Handle As Integer, ByVal Location As MapWindow.Interfaces.ClickLocation, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.LegendDoubleClick

    End Sub

    Public Sub LegendMouseDown(ByVal Handle As Integer, ByVal Button As Integer, ByVal Location As MapWindow.Interfaces.ClickLocation, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.LegendMouseDown

    End Sub

    Public Sub LegendMouseUp(ByVal Handle As Integer, ByVal Button As Integer, ByVal Location As MapWindow.Interfaces.ClickLocation, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.LegendMouseUp

    End Sub

    Public Sub MapDragFinished(ByVal Bounds As System.Drawing.Rectangle, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.MapDragFinished

    End Sub

    Public Sub MapExtentsChanged() Implements MapWindow.Interfaces.IPlugin.MapExtentsChanged

    End Sub

    'start drawing the polyline
    Public Sub MapMouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal screenX As Integer, ByVal screenY As Integer, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.MapMouseDown

        'Only enable drawing when the 'draw path' menu is activated
        If _boolDraw = False Then Exit Sub

        'Button number 2 means right click - finish drawing the path
        If Button = 2 Then

            If _boolFirstPointClick = False Then Exit Sub

            'convert screen coordinates to map coordinates
            _mapWin.View.PixelToProj(screenX, screenY, _lastStartX, _lastStartY)

            'add the point to the list of points
            Dim endPoint As New MapWinGIS.Point()
            endPoint.x = _lastStartX
            endPoint.y = _lastStartY
            _listOfPoints.Add(endPoint)

            'change the cursor back to normal
            _mapWin.View.MapCursor = tkCursor.crsrMapDefault

            _mapWin.View.Draw.ClearDrawings()
            'Create the shapefile and add it to the map
            Dim pathName As String = GetNewPathName()
            Dim pathShapefile As Shapefile = CreatePathShapefile(pathName, _listOfPoints)

            _boolDraw = False
            _boolFirstPointClick = False

            'enable the draw graph and compare ski path
            _menuDrawGraph = True
        Else
            'Other buttons mean left click
            _drawingHandle = _mapWin.View.Draw.NewDrawing(MapWinGIS.tkDrawReferenceList.dlSpatiallyReferencedList)

            'convert screen coordinates to map coordinates
            _mapWin.View.PixelToProj(screenX, screenY, _lastStartX, _lastStartY)

            'add the point to the list of points
            Dim curPoint As New MapWinGIS.Point()
            curPoint.x = _lastStartX
            curPoint.y = _lastStartY
            _listOfPoints.Add(curPoint)

            _boolFirstPointClick = True
        End If
    End Sub

    Public Sub MapMouseMove(ByVal ScreenX As Integer, ByVal ScreenY As Integer, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.MapMouseMove

        If _boolDraw = True And _boolFirstPointClick = True Then

            _mapWin.View.PixelToProj(ScreenX, ScreenY, _curX, _curY)

            'clear previously drawn line and draw a new line on the map
            _mapWin.View.Draw.ClearDrawing(_drawingHandle)
            _drawingHandle = _mapWin.View.Draw.NewDrawing(MapWinGIS.tkDrawReferenceList.dlSpatiallyReferencedList)

            _mapWin.View.Draw.DrawLine(_lastStartX, _lastStartY, _curX, _curY, 2, Drawing.Color.Red)

        End If
    End Sub

    Public Sub MapMouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal ScreenX As Integer, ByVal ScreenY As Integer, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.MapMouseUp

        'Dim endX As Double
        'Dim endY As Double
        '_mapWin.View.PixelToProj(ScreenX, ScreenY, endX, endY)

        ''clear previously drawn line and draw a new line on the map
        'If _drawingHandle <> -1 Then
        '    _mapWin.View.Draw.ClearDrawing(_drawingHandle)
        '    _drawingHandle = _mapWin.View.Draw.NewDrawing(MapWinGIS.tkDrawReferenceList.dlSpatiallyReferencedList)
        'End If

        '_mapWin.View.Draw.DrawLine(_startX, _startY, endX, endY, 2, Drawing.Color.Red)

    End Sub


    Private Sub LoadDEM()
        Dim demFileName As String
        Dim OpenFileDialog1 As New System.Windows.Forms.OpenFileDialog
        OpenFileDialog1.Title = "Please select a DEM"
        OpenFileDialog1.Filter = DemGrid.CdlgFilter

        If OpenFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then

            demFileName = OpenFileDialog1.FileName

            Try
                Dim demLayer As Layer = _mapWin.Layers.Add(demFileName, "DEM")

                'change the map cursor mode
                _mapWin.View.CursorMode = tkCursorMode.cmNone

                'set the global DEMGrid object
                DemGrid = demLayer.GetGridObject()

                'enable the draw path menu
                _menuDrawPath = True

            Catch ex As Exception
                MsgBox("There was an error loading the DEM.")
            End Try
        End If
    End Sub

    Private Function GetNewPathName() As String

        Dim appDataFolder As String = System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
        Dim shapefileFolder As String = appDataFolder + "\" + "SkiPathFinder"
        If Not System.IO.Directory.Exists(shapefileFolder) Then
            System.IO.Directory.CreateDirectory(shapefileFolder)
        End If

        Dim pathNum As Integer = 1

        Dim pathFileName As String = shapefileFolder + "\" + "path" & pathNum & ".shp"

        For pathNum = 1 To 100
            pathFileName = shapefileFolder + "\" + "path" & pathNum & ".shp"

            If Not File.Exists(pathFileName) Then
                Exit For
            End If
        Next

        Return pathFileName
    End Function

    Private Function CreatePathShapefile(ByVal pathName As String, ByVal listOfPoints As List(Of MapWinGIS.Point)) As Shapefile

        Dim sf As New Shapefile()

        sf.CreateNew(pathName, ShpfileType.SHP_POLYLINE)
        sf.StartEditingShapes()

        'add the path name attribute
        Dim fld As New Field()
        fld.Name = "name"
        fld.Type = FieldType.STRING_FIELD
        sf.EditInsertField(fld, sf.NumFields)

        'Add the normal field
        Dim fldNormal As New Field()
        fldNormal.Name = "normal"
        fldNormal.Type = FieldType.DOUBLE_FIELD
        sf.EditInsertField(fldNormal, sf.NumFields)

        'Add the moderate field
        Dim fldModerate As New Field()
        fldModerate.Name = "moderate"
        fldModerate.Type = FieldType.DOUBLE_FIELD
        sf.EditInsertField(fldModerate, sf.NumFields)

        'Add the high field
        Dim fldHigh As New Field()
        fldHigh.Name = "high"
        fldHigh.Type = FieldType.DOUBLE_FIELD
        sf.EditInsertField(fldHigh, sf.NumFields)

        'Add the very high field
        Dim fldVeryHigh As New Field()
        fldVeryHigh.Name = "very high"
        fldVeryHigh.Type = FieldType.DOUBLE_FIELD
        sf.EditInsertField(fldVeryHigh, sf.NumFields)

        Dim shp As New MapWinGIS.Shape()
        shp.ShapeType = ShpfileType.SHP_POLYLINE

        For Each pt As MapWinGIS.Point In listOfPoints
            shp.InsertPoint(pt, shp.numPoints)
        Next

        'add shape to shapefile
        sf.EditInsertShape(shp, sf.NumShapes)

        'get the short path name
        Dim shortPathName = Path.GetFileNameWithoutExtension(pathName)
        sf.EditCellValue(0, 0, shortPathName)

        sf.Projection = DemGrid.Header.Projection

        'analyze path risk for this shapefile
        '(1) calculate the elevation profile table
        Dim profileTable As DataTable = CalculateElevationProfile(sf, DemGrid)
        '(2) analyze the risk of the ski path
        Dim riskArray As Double() = AnalyzeRisk(profileTable)
        '(3) update the dbf table with the risk values
        UpdateRiskValues(riskArray, sf)

        sf.StopEditingShapes()

        sf.Close()
        sf.Save()

        'assign the lastSkiPathShapefile
        _lastSkiShapefile = sf

        'add the shapefile to map and legend
        Dim pathLayer As Layer = _mapWin.Layers.Add(pathName)
        pathLayer.Name = shortPathName

        Return sf

    End Function

    ''' <summary>
    ''' This function takes calculated risk values in the risk array and populates
    ''' risk values in the dbf file.
    ''' </summary>
    
    Private Sub UpdateRiskValues(ByVal riskArray As Double(), ByVal shpFile As Shapefile)

        shpFile.StartEditingTable()

        shpFile.EditCellValue(1, 0, riskArray(0))
        shpFile.EditCellValue(2, 0, riskArray(1))
        shpFile.EditCellValue(3, 0, riskArray(2))
        shpFile.EditCellValue(4, 0, riskArray(3))

        shpFile.StopEditingTable()

    End Sub

    'Private Function CreateTable() As DataTable
    '    'create the output dataTable
    '    Dim table As New DataTable
    '    table.Columns.Add(New DataColumn("distance"))
    '    table.Columns.Add(New DataColumn("elevation"))
    '    table.Columns.Add(New DataColumn("slope"))
    '    Return table
    'End Function

    ''' <summary>
    ''' Calculate the elevation profile for the complete path shapefile
    ''' </summary>
    ''' <param name="shpFile"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CalculateElevationProfile(ByVal shpFile As Shapefile, ByVal demGrid As Grid) As DataTable

        '(1) Create the empty result DataTable
        Dim table As New DataTable
        table.Columns.Add(New DataColumn("distance"))
        table.Columns.Add(New DataColumn("elevation"))
        table.Columns.Add(New DataColumn("slope"))

        '(2) Get the polyline shape and loop through each straight line section
        Dim polyline As MapWinGIS.Shape = shpFile.Shape(0)

        For i As Integer = 0 To polyline.numPoints - 1
            If Not (i = polyline.numPoints - 1) Then
                Dim startPoint As MapWinGIS.Point = polyline.Point(i)
                Dim endPoint As MapWinGIS.Point = polyline.Point(i + 1)

                ExtractElevation(startPoint.x, startPoint.y, endPoint.x, endPoint.y, demGrid, table)

            End If
        Next
        Return table
    End Function

    Private Shared Sub ExtractElevation(ByVal startX As Double, ByVal startY As Double, _
                                      ByVal endX As Double, ByVal endY As Double, _
                                      ByVal demGrid As MapWinGIS.Grid, ByRef table As DataTable)
        'get the cell size of the dem Grid
        Dim cellX As Double = demGrid.Header.dX
        Dim cellY As Double = demGrid.Header.dY

        Dim startRow As Double
        Dim endRow As Double
        Dim startCol As Double
        Dim endCol As Double
        Dim curRow As Double
        Dim curCol As Double
        Dim curDist As Double = 0

        demGrid.ProjToCell(startX, startY, startCol, startRow)
        demGrid.ProjToCell(endX, endY, endCol, endRow)

        'we assume the number of points in the segment is 100 points
        Dim numberOfPoints As Integer = 100

        'declare arrays for storing distance, elevation, slope
        Dim distanceArray(numberOfPoints - 1) As Double
        Dim elevationArray(numberOfPoints - 1) As Double
        Dim slopeArray(numberOfPoints - 1) As Double

        Dim constXDif As Double = (endCol - startCol) / numberOfPoints

        Dim constYDif As Double = (endRow - startRow) / numberOfPoints

        Dim constDiagDif As Double = Math.Sqrt(constXDif ^ 2 + constYDif ^ 2)

        For j As Integer = 0 To numberOfPoints - 1

            If j = 0 Then
                curCol = startCol
                curRow = startRow
            Else
                curCol = curCol + constXDif
                curRow = curRow + constYDif
                curDist = curDist + constDiagDif
            End If

            distanceArray(j) = curDist
            elevationArray(j) = demGrid.Value(curCol, curRow)

        Next

        'find the slope
        For k As Integer = 0 To distanceArray.Length - 1

            If k = distanceArray.Length - 1 Then
                slopeArray(k) = slopeArray(k - 1)
                Exit For
            End If

            Dim elevationDif As Double = Math.Abs(elevationArray(k) - elevationArray(k + 1))
            Dim slope As Double = elevationDif / constDiagDif
            slopeArray(k) = slope
        Next

        If table.Rows.Count = 0 Then

            For i = 0 To elevationArray.Length - 1
                Dim tableRow As DataRow = table.NewRow()
                tableRow.Item(0) = distanceArray(i)
                tableRow.Item(1) = elevationArray(i)
                tableRow.Item(2) = slopeArray(i)
                table.Rows.Add(tableRow)
            Next

        Else

            Dim initialDistance As Double = table.Rows(table.Rows.Count - 1).Item(0)
            For i = 1 To elevationArray.Length - 1
                Dim tableRow As DataRow = table.NewRow()
                tableRow.Item(0) = distanceArray(i) + initialDistance
                tableRow.Item(1) = elevationArray(i)
                tableRow.Item(2) = slopeArray(i)
                table.Rows.Add(tableRow)
            Next
        End If

    End Sub

    ''' <summary>
    ''' Analyze the risk of a ski path. The result array has 4 items:
    ''' normal risk percentage, moderate risk percentage, high risk percentage
    ''' and very high risk percentage
    ''' </summary>
    ''' <param name="table"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function AnalyzeRisk(ByVal table As DataTable) As Double()

        'create the result array
        Dim riskArray(4) As Double

        Dim normalCounter As Integer = 0
        Dim moderateCounter As Integer = 0
        Dim highCounter As Integer = 0
        Dim veryHighCounter As Integer = 0
        Dim numRows As Integer = table.Rows.Count

        For Each row As DataRow In table.Rows
            Dim slope As Double = row(2)

            If slope < 5 Then
                normalCounter += 1
            ElseIf slope < 10 Then
                moderateCounter += 1
            ElseIf slope < 20 Then
                highCounter += 1
            Else
                veryHighCounter += 1
            End If
        Next

        riskArray(0) = (normalCounter / numRows) * 100
        riskArray(1) = (moderateCounter / numRows) * 100
        riskArray(2) = (highCounter / numRows) * 100
        riskArray(3) = (veryHighCounter / numRows) * 100

        Return riskArray

    End Function

    Public Sub Message(ByVal msg As String, ByRef Handled As Boolean) Implements MapWindow.Interfaces.IPlugin.Message

    End Sub

    Public ReadOnly Property Name() As String Implements MapWindow.Interfaces.IPlugin.Name
        Get
            Return "SkiPathFinder"
        End Get
    End Property

    Public Sub ProjectLoading(ByVal ProjectFile As String, ByVal SettingsString As String) Implements MapWindow.Interfaces.IPlugin.ProjectLoading

    End Sub

    Public Sub ProjectSaving(ByVal ProjectFile As String, ByRef SettingsString As String) Implements MapWindow.Interfaces.IPlugin.ProjectSaving

    End Sub

    Public ReadOnly Property SerialNumber() As String Implements MapWindow.Interfaces.IPlugin.SerialNumber
        Get
            Return "SkiPathFinder1"
        End Get
    End Property

    Public Sub ShapesSelected(ByVal Handle As Integer, ByVal SelectInfo As MapWindow.Interfaces.SelectInfo) Implements MapWindow.Interfaces.IPlugin.ShapesSelected

    End Sub

    Public Sub Terminate() Implements MapWindow.Interfaces.IPlugin.Terminate
        _mapWin.Menus.Remove("Ski Path Finder")
        _mapWin.Layers.Clear()

    End Sub

    Public ReadOnly Property Version() As String Implements MapWindow.Interfaces.IPlugin.Version
        Get
            Return "1.0"
        End Get
    End Property
End Class
